﻿using System;
using System.Collections.Generic;

using Settings = System.Collections.Generic.Dictionary<string, string>;
using System.Globalization;
using System.Text;

namespace System.Web.ScriptBuilder
{
	public abstract class ScriptCompressor
	{
		private class InternalAjaxMinifier : ScriptCompressor
		{
			public override string Compress(string code, Settings settings, Action<string> logger)
			{
				var kind = "js";
				if (settings.ContainsKey("kind"))
					kind = settings["kind"];

				var minifier = new Microsoft.Ajax.Utilities.Minifier();

				if (string.Equals(kind, "js", StringComparison.InvariantCultureIgnoreCase))
				{
					var codeSettings = new Microsoft.Ajax.Utilities.CodeSettings
					{
						CollapseToLiteral = TryParseBool(settings, "CollapseToLiteral", true),
						CombineDuplicateLiterals = TryParseBool(settings, "CombineDuplicateLiterals", false),
						EvalTreatment = TryParseEnum(settings, "EvalTreatment", Microsoft.Ajax.Utilities.EvalTreatment.Ignore),
						IndentSize = TryParseInt(settings, "IndentSize", 4),
						InlineSafeStrings = TryParseBool(settings, "InlineSafeStrings", true),
						LocalRenaming = TryParseEnum(settings, "LocalRenaming", Microsoft.Ajax.Utilities.LocalRenaming.CrunchAll),
						MacSafariQuirks = TryParseBool(settings, "MacSafariQuirks", true),
						MinifyCode = TryParseBool(settings, "MinifyCode", true),
						OutputMode = TryParseEnum(settings, "OutputMode", Microsoft.Ajax.Utilities.OutputMode.SingleLine),
						PreserveFunctionNames = TryParseBool(settings, "PreserveFunctionNames", false),
						RemoveFunctionExpressionNames = TryParseBool(settings, "RemoveFunctionExpressionNames", true),
						RemoveUnneededCode = TryParseBool(settings, "RemoveUnneededCode", true),
						StripDebugStatements = TryParseBool(settings, "StripDebugStatements", true)
					};

					code = minifier.MinifyJavaScript(code, codeSettings);

					if (logger != null)
					{
						foreach (var msg in minifier.Errors)
						{
							logger(msg);
						}
					}

					return code;
				}
				else if (string.Equals(kind, "css", StringComparison.InvariantCultureIgnoreCase))
				{
					var cssSettings = new Microsoft.Ajax.Utilities.CssSettings
					{
						ColorNames = TryParseEnum(settings, "ColorNames", Microsoft.Ajax.Utilities.CssColor.Strict),
						CommentMode = TryParseEnum(settings, "CommentMode", Microsoft.Ajax.Utilities.CssComment.None),
						ExpandOutput = TryParseBool(settings, "ExpandOutput", false),
						IndentSpaces = TryParseInt(settings, "IndentSpaces", 4),
						Severity = TryParseInt(settings, "Severity", 1),
						TermSemicolons = TryParseBool(settings, "TermSemicolons", false)
					};

					code = minifier.MinifyStyleSheet(code, cssSettings);

					if (logger != null)
					{
						foreach (var msg in minifier.Errors)
						{
							logger(msg);
						}
					}

					return code;
				}
				else
					throw new InvalidOperationException(Loc.GetString("Exception_ScriptCompressorUnknownScriptKind", kind));
			}
		}
		private class InternalYuiCompressor : ScriptCompressor
		{
			public override string Compress(string code, Settings settings, Action<string> logger)
			{
				var kind = "js";
				if (settings.ContainsKey("kind"))
					kind = settings["kind"];

				if (string.Equals(kind, "js", StringComparison.InvariantCultureIgnoreCase))
				{
					var compressor = new Yahoo.Yui.Compressor.JavaScriptCompressor(
						code,
						TryParseBool(settings, "isVerboseLogging", true),
						TryParseEncoding(settings, "encoding", Encoding.Default),
						TryParseCulture(settings, "threadCulture", CultureInfo.CreateSpecificCulture("en-GB")),
						TryParseBool(settings, "isEvalIgnored", false), 
						null
					);

					code = compressor.Compress(
						TryParseBool(settings, "isObfuscateJavascript", true),
						TryParseBool(settings, "preserveAllSemicolons", false),
						TryParseBool(settings, "disableOptimizations", false),
						TryParseInt(settings, "lineBreakPosition", -1)
					);

					var reporter = compressor.ErrorReporter as Yahoo.Yui.Compressor.CustomErrorReporter;
					if (logger != null && reporter != null)
					{
						foreach (var msg in reporter.ErrorMessages)
						{
							logger(msg);
						}
					}

					return code;
				}
				else if (string.Equals(kind, "css", StringComparison.InvariantCultureIgnoreCase))
				{
					return Yahoo.Yui.Compressor.CssCompressor.Compress(
						code,
						TryParseInt(settings, "columnWidth", 0),
						TryParseEnum(settings, "cssCompressionType", Yahoo.Yui.Compressor.CssCompressionType.StockYuiCompressor)
					);
				}
				else
					throw new InvalidOperationException(Loc.GetString("Exception_ScriptCompressorUnknownScriptKind", kind));
			}
		}

		public static ScriptCompressor AjaxMinifier { get; private set; }
		public static ScriptCompressor YuiCompressor { get; private set; }
		public static ScriptCompressor None { get { return null; } }

		static ScriptCompressor()
		{
			AjaxMinifier = new ScriptCompressor.InternalAjaxMinifier();
			YuiCompressor = new ScriptCompressor.InternalYuiCompressor();
		}

		public abstract string Compress(string code, Settings settings, Action<string> logger);

		private static bool TryParseBool(Settings settings, string key, bool defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			bool value;
			if (!bool.TryParse(str, out value))
				return defaultValue;
			return value;
		}
		private static int TryParseInt(Settings settings, string key, int defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			int value;
			if (!int.TryParse(str, out value))
				return defaultValue;
			return value;
		}
		private static string TryGetString(Settings settings, string key, string defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			return str;
		}
		private static CultureInfo TryParseCulture(Settings settings, string key, CultureInfo defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			try
			{
				CultureInfo value = CultureInfo.GetCultureInfo(str);
				if (value == null)
					return defaultValue;
				return value;
			}
			catch (ArgumentException) { return defaultValue; }
		}
		private static Encoding TryParseEncoding(Settings settings, string key, Encoding defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			try
			{
				Encoding value = Encoding.GetEncoding(str);
				if (value == null)
					return defaultValue;
				return value;
			}
			catch (ArgumentException) { return defaultValue; }
		}
		private static EnumT TryParseEnum<EnumT>(Settings settings, string key, EnumT defaultValue)
		{
			string str;
			if (!settings.TryGetValue(key, out str))
				return defaultValue;

			try
			{
				EnumT value = (EnumT)Enum.Parse(typeof(EnumT), str, true);
				return value;
			}
			catch (ArgumentException)
			{
				return defaultValue;
			}
		}
	}
}
